﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace SmartRoute.Optimization.HarmonySearch.Internal
{
    //TODO? Store melodies in heaped array instead of array
    //Pro: slight improvement in speed for large bank sizes
    //Cons: programmer time spent on this instead of something more useful
    // Harmony search uses small population sizes anyway thus little gain

    //Goal: only one stateful public function "advance()"
    //Identical states should, with well behaved delegates and melody type, be identical after one advance

    /// <summary>
    /// State structure that permits in-place manipulation of the state.
    /// </summary>
    [Serializable]
    public class HarmonySimulationState<Melody>
    {
        /// <summary>
        /// The fitness test used to advance the simulation state.
        /// </summary>
        protected readonly FitnessFunction<Melody> fitTest;

        /// <summary>
        /// Generates the improvisators on bank items.
        /// </summary>
        protected readonly Improviser<Melody> improviser;

        /// <summary>
        /// An enumerator that makes randomly generated melodies
        /// </summary>
        protected readonly IEnumerator<Melody> generator;

        /// <summary>
        /// A function that returns a stream of melodies given an integer seed
        /// </summary>
        private Generator<Melody> generatorGen;

        /// <summary>
        /// The random seed used by this particular simulation state
        /// </summary>
        protected readonly int randomSeed;

        /// <summary>
        /// The random number generator used by this simulation state
        /// </summary>
        private System.Random rng;

        /// <summary>
        /// Stores a single melody with cached fitness
        /// </summary>
        protected struct BankItem
        {
            private readonly Melody value;

            /// <summary>
            /// Generates the improvisations over this melody
            /// </summary>
            private IEnumerator<Melody> improvs;
            private readonly double fitness;
            
            /// <summary>
            /// Returns the cached fitness
            /// </summary>
            public double Fitness { get { return fitness; } }

            /// <summary>
            /// Statefully gives an improvisation over this melody
            /// </summary>
            public Melody Improvise { get { improvs.MoveNext(); return improvs.Current; } }

            /// <summary>
            /// Returns the melody this struct is storing
            /// </summary>
            public Melody Value {get {return value;}}

            /// <summary>
            /// Struct constructor that stores the melody and its cached fitness
            /// </summary>
            public BankItem(Melody item, double fit, IEnumerable<Melody> improvisator)
            {
                value = item;
                fitness = fit;
                improvs = improvisator.GetEnumerator();
            }
        }

        /// <summary>
        /// Makes a bank item with a different seed from the given bank item, keeping the cached fitness
        /// </summary>
        private BankItem branchBankItemOnSeed(BankItem toBeBranched, int seed)
        {
            return new BankItem(toBeBranched.Value, toBeBranched.Fitness, improviser(randomSeed, toBeBranched.Value));
        }

        /// <summary>
        /// Calculates the fitness of a melody and caches it in the BankItem
        /// </summary>
        private BankItem makeBankItem(Melody item)
        {
            return new BankItem(item, fitTest(item), improviser(randomSeed, item));
        }

        /// <summary>
        /// Generates a new BankItem at random
        /// </summary>
        private BankItem invent()
        {
            var o = makeBankItem(generator.Current);
            generator.MoveNext();
            return o;
        }

        /// <summary>
        /// The harmony memory bank, which keeps track of known solutions and their fitnesses
        /// </summary>
        private BankItem[] harmonyMemoryBank; 

        /// <summary>
        /// A pointer to the current champion in the memory bank
        /// </summary>
        private int pointerMostFit  = 0;

        /// <summary>
        /// A pointer to the value that should be overwritten
        /// </summary>
        private int pointerLeastFit = 0;

        /// <summary>
        /// Sets the pointers to the least and most fit values.
        /// Should only be called by the constructor.
        /// </summary>
        private void initializePointers()
        {
            for (int i = 1; i < harmonyMemoryBank.Length; i++)
            {
                if (harmonyMemoryBank[i].Fitness < harmonyMemoryBank[pointerLeastFit].Fitness)
                    pointerLeastFit = i;
                else if (harmonyMemoryBank[i].Fitness > harmonyMemoryBank[pointerMostFit].Fitness)
                    pointerMostFit = i;
            }
        }

        /// <summary>
        /// The average fitness of all melodies in the harmony memory bank
        /// </summary>
        public double AvgFitness
        {
            get
            {
                double result = 0;
                foreach (BankItem e in harmonyMemoryBank) result += e.Fitness;
                return (result / harmonyMemoryBank.Length);
            }
        }

        /// <summary>
        /// Result from a destructive advance after a consideration
        /// BankUnchanged indicates that the consideration was rejected from the memory bank
        /// BankUpdate indicates that the consideration overwrote a remembered memory
        /// BankNewBest indicates that the consideration overwrote a remembered memory and is the new champion
        /// </summary>
        public enum StateAdvanceResult
        {
            BankUnchanged,
            BankUpdated,
            BankNewBest
        }

        /// <summary>
        /// Corrects the pointer values after the least fit value is replaced
        /// </summary>
        private StateAdvanceResult updatePointers()
        {
            StateAdvanceResult result = StateAdvanceResult.BankUpdated;
            if (harmonyMemoryBank[pointerLeastFit].Fitness > harmonyMemoryBank[pointerMostFit].Fitness)
            {
                pointerMostFit = pointerLeastFit;
                result = StateAdvanceResult.BankNewBest;
            }
            pointerLeastFit = 0;
            for (int i = 1; i < harmonyMemoryBank.Length; i++)
                if (harmonyMemoryBank[i].Fitness < harmonyMemoryBank[pointerLeastFit].Fitness)
                    pointerLeastFit = i;
            return result;
        }
        //private StateAdvanceResult update(Melody newMelody)
        //{
        //    var considering = makeBankItem(newMelody);
        //    if (considering.Fitness > harmonyMemoryBank[pointerLeastFit].Fitness)
        //    {
        //        harmonyMemoryBank[pointerLeastFit] = considering;
        //        return updatePointers();
        //    } else return StateAdvanceResult.BankUnchanged;
        //}

        /// <summary>
        /// Adds a new melody to the harmony memory bank, after it is wrapped in a BankItem
        /// </summary>
        private StateAdvanceResult update(BankItem newMelody)
        {
            if (newMelody.Fitness > harmonyMemoryBank[pointerLeastFit].Fitness)
            {
                harmonyMemoryBank[pointerLeastFit] = newMelody;
                return updatePointers();
            }
            else return StateAdvanceResult.BankUnchanged;
        }

        /// <summary>
        /// Builds a simulation state out of a bank of known starting melodies.
        /// </summary>
        /// <param name="newBank">The starting harmony memory bank this is to be built with</param>
        /// <param name="seed"></param>
        /// <param name="fit"></param>
        /// <param name="improv"></param>
        /// <param name="gen"></param>
        public HarmonySimulationState(Melody[] newBank, int seed, FitnessFunction<Melody> fit, Improviser<Melody> improv, Generator<Melody> gen)
        {
            randomSeed = seed;
            improviser = improv;
            generatorGen = gen;
            generator = gen(seed).GetEnumerator();
            generator.MoveNext();
            fitTest = fit;
            harmonyMemoryBank = new BankItem[(newBank.Length)];
            for(int i = 0; i < newBank.Length; i++) harmonyMemoryBank[i] = makeBankItem(newBank[i]);
            initializePointers();
            rng = new System.Random(seed);
        }
        //DRY VIOLATION
        public HarmonySimulationState(uint bankSize, int seed, FitnessFunction<Melody> fit, Improviser<Melody> improv, Generator<Melody> gen)
        {
            randomSeed = seed;
            improviser = improv;
            generatorGen = gen;
            generator = gen(seed).GetEnumerator();
            generator.MoveNext();
            fitTest = fit;
            harmonyMemoryBank = new BankItem[bankSize];
            for (uint i = 0; i < bankSize; i++) harmonyMemoryBank[i] = invent();
            initializePointers();
            rng = new System.Random(seed);
        }
        private HarmonySimulationState(BankItem[] exBank, int seed, FitnessFunction<Melody> fit, Improviser<Melody> improv, Generator<Melody> gen)
        {
            harmonyMemoryBank = new BankItem[exBank.Length];
            exBank.CopyTo(harmonyMemoryBank, 0);
            generator = gen(seed).GetEnumerator();
            generator.MoveNext();
            for (int i = 0; i < harmonyMemoryBank.Length; i++)
            {
                harmonyMemoryBank[i] = branchBankItemOnSeed(harmonyMemoryBank[i], seed);
            }
        }
        public HarmonySimulationState<Melody> branchBySeed(int newSeed)
        {
            return new HarmonySimulationState<Melody>(this.harmonyMemoryBank, newSeed, fitTest, improviser, generatorGen);
        }

        /// <summary>
        /// Acts as a copy constructor before the state has been advanced
        /// Allows a simulator to work with simulation state in a pure way by destroying
        ///  the branched state rather than the original state
        /// Delegates and melody type must be well behaved
        /// </summary>
        public HarmonySimulationState<Melody> branch()
        {
            return this.branchBySeed(randomSeed);
        }

        /// <summary>
        /// Chooses a random item from the harmony memory bank
        /// </summary>
        protected BankItem pickRandom()
        {
            return harmonyMemoryBank[rng.Next(0,harmonyMemoryBank.Length)];
        }

        /// <summary>
        /// Destructively advances this simulation state
        /// Once this is called, the state is lost forever, and this state can no longer be copied.
        /// </summary>
        /// <param name="considerationRate">A chance between 0 and 1 that a known melody will be improvised over</param>
        /// <returns>What changes happened during the destructive update.</returns>
        public StateAdvanceResult consider(double considerationRate)
        {
            var outcome = rng.NextDouble() > considerationRate ? this.invent() : makeBankItem(this.pickRandom().Improvise);
            return update(outcome);
        }
    }
}
